// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rbchat.logic.chat_root.sendvoice;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import com.eva.android.DelayedHandler;
import com.eva.android.VoiceRecorder;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.widget.WidgetUtils.ToastType;
import com.eva.android.widget.alert.AlertDialog;
import com.eva.epc.common.file.FileHelper;
import com.x52im.rbchat.R;
import com.x52im.rbchat.utils.PromtHelper;
import com.x52im.rbchat.logic.chat_root.sendimg.PreviewAndSendActivity;

import java.io.File;
import java.io.FilenameFilter;
import java.text.SimpleDateFormat;

/**
 * 语音留言的录音采集的完整UI包装实现类.
 * 
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 */
public abstract class SendVoiceWrapper
{
	private final static String TAG = SendVoiceWrapper.class.getSimpleName();
	
	/** 录音时长显示格式 */
	public static final SimpleDateFormat duraionFormatter = new SimpleDateFormat("mm:ss");// 分：秒
	
	private Activity parentActivity = null;
	/** 在群组聊天中，此参数存放的是群的id，否则一对一聊天时存放的是聊天对象的uid */
	private String friendUid = null;
	/** 功能应用场景:如用于陌生人聊天、一对一好友聊、世界频道聊等.
	 * @see {@link PreviewAndSendActivity} */
	private String usedFor = "";
	
//	private ViewGroup layoutOfPrepareProgress = null;
	private ViewGroup layoutOfRecording = null;
	
	private Animation animLightEfect = null;
	private ImageView viewVolumn = null;
	private ImageView viewLightEfect = null;
	private TextView viewDuration = null;
	private View layoutSend = null;
	private Button btnCancel = null;
	
	/** 录音可重用封装类 */
	private VoiceRecorder voiceRecorderWrapper = null;
	
	// 到达最大录音时长的延迟器
	private DelayedHandler delayedHandler = null;
	
	public SendVoiceWrapper(Activity parentActivity, String friendUid, String usedFor)
	{
		this.parentActivity = parentActivity;
		this.friendUid = friendUid;
		this.usedFor = usedFor;
		
		initVoiceRecorderWrapper();
		initViews();
		initListeners();
		
//		//
//		start();
		
		// 尝试清理过期的临时录音文件
		// 使用异步线程让其在后台执行，防止阻塞主流程
		tryClearOldFilesAsync(parentActivity, SendVoiceHelper.MAX_TIME_FOR_OLD_VOICE_FILE
				, SendVoiceHelper.TEMP_VOICE_FILE_NAME_PREFIX);
	}
	
	public static void tryClearOldFilesAsync(final Context parentActivity
			, final long MAX_TIME_FOR_OLD_VOICE_FILE, final String prefix)
	{
		// 尝试清理过期的临时录音文件
		// 使用异步线程让其在后台执行，防止阻塞主流程
		new AsyncTask<Object, Object, Object>()
		{
			@Override
			protected Object doInBackground(Object... params)
			{
				tryClearOldFiles(parentActivity, MAX_TIME_FOR_OLD_VOICE_FILE
						, prefix);
				return null;
			}
		}.execute();
	}
	
	/**
	 * 为保证没有被正常处理的语音临时文件不要老存放在SD卡上（占用空间）而所作的清理处理.
	 */
	public static void tryClearOldFiles(Context parentActivity, long MAX_TIME_FOR_OLD_VOICE_FILE, String prefix)
	{
		try
		{
			File voiceSavedDir = new File(SendVoiceHelper.getSendVoiceSavedDir(parentActivity));
			if(voiceSavedDir.exists())
			{
				// TODO 应该只过滤音频录音文件如.amr（目前不过滤也不影响，删除所有也没有问题）
				File[] files = voiceSavedDir.listFiles(new TempVoiceFilter(prefix));
				if(files != null && files.length > 0)
				{
					for(File f : files)
					{
						// 是临时文件
						if(f.isFile() && f.exists())
						{
							long delta = System.currentTimeMillis() - f.lastModified();
							if(delta >= MAX_TIME_FOR_OLD_VOICE_FILE)
							{
								Log.d(TAG, "文件"+f.getAbsolutePath()+"距今已"+delta
									+"毫秒(超过了最大生存期"+MAX_TIME_FOR_OLD_VOICE_FILE+")了，即将被删除！");
								f.delete();
							}
							else
							{
								Log.d(TAG, "文件"+f.getAbsolutePath()+"距今只有"+delta
										+"毫秒(没有超过最大生存期"+MAX_TIME_FOR_OLD_VOICE_FILE+")，无需被删除.");
							}
						}
					}
				}
			}
		}
		catch (Exception e)
		{
			Log.d(TAG, "清理目录"+SendVoiceHelper.getSendVoiceSavedDir(parentActivity)+"下的临时语音文件时出错了，"+e.getMessage(), e);
		}
	}
	
	/**
	 * 初始化录音模块.
	 */
	protected void initVoiceRecorderWrapper()
	{
		voiceRecorderWrapper = new VoiceRecorder(parentActivity){
			/**
			 * 捕获音量.
			 * 
			 * @param volumn 声音的振幅
			 * @see MediaRecorder#getMaxAmplitude()
			 */
			@Override
			protected void volumnCaptured(double volumn)
			{
				refreshVolumnAnimationToUI(viewVolumn, volumn);
			}
			
			/**
			 * 录音时长通知.
			 * 
			 * @param duration
			 */
			@Override
			protected void durationChanged(long duration)
			{
				refreshDurationToUI(duraionFormatter, viewDuration, duration);
			}
		};
	}
	
	/**
	 * 初始化ui.
	 */
	protected void initViews()
	{
//		layoutOfPrepareProgress = (ViewGroup)findViewById(R.id.chatting_list_view_record_frame_llPrepareProgress);
		layoutOfRecording = (ViewGroup)findViewById(R.id.chatting_list_view_record_frame_llRecording);
		animLightEfect = AnimationUtils.loadAnimation(parentActivity, R.anim.chatting_list_view_voice_recording_light_rotate);
		layoutSend = findViewById(R.id.chatting_list_view_record_frame_llSend);
		viewVolumn = (ImageView)findViewById(R.id.chatting_list_view_record_frame_viewVolumn);
		viewLightEfect = (ImageView)findViewById(R.id.chatting_list_view_record_frame_viewLightEfect);
		viewDuration = (TextView)findViewById(R.id.chatting_list_view_record_frame_viewDuration);
		btnCancel = (Button)findViewById(R.id.chatting_list_view_record_frame_btnCalcel);
		
//		viewLightEfect.startAnimation(animLightEfect);
//		// 注意：上面xml中定义interpolator会出现转一圈后停顿一下。
//		// Android有这么一个类LinearInerpolator：这个类是Inerpolator的子类，是实现匀速动画的
//		// 关键。但是不能通过XML中的那个android:interpolator属性去设置，必须在java代码中去实现
//		animLightEfect.setInterpolator(new LinearInterpolator());
	}
	
	/**
	 * 初始化ui事件处理.
	 */
	protected void initListeners()
	{
		layoutSend.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View v)
			{
				sendVoice();
			}
		});
		btnCancel.setOnClickListener(new OnClickListener(){
			@Override
			public void onClick(View v)
			{
				cancelRecording(true);
			}
		});
	}
	
	/**
	 * 发送语音.
	 * 
	 * @see #sendVoiceImpl(String)
	 */
	public void sendVoice()
	{
		// 停止录音
		String tempVoicePath = stop(false);
		// 关闭录音ui
		closeUI();
		
		// 发送语音
		sendVoiceImpl(tempVoicePath);
	}
	/**
	 * 发送语音核心实现方法.
	 * 
	 * @param tempVoicePath 要发送语音的文件路径
	 */
	private void sendVoiceImpl(String tempVoicePath)
	{
		// 开始发送
		if(tempVoicePath != null)
		{
			// 发送语音留言消息
			new SendVoiceProcessor(parentActivity
					, usedFor
					, friendUid, tempVoicePath, voiceRecorderWrapper.getDuration()).doSend();
		}
		else
		{
			WidgetUtils.showToast(parentActivity
					, parentActivity.getString(R.string.chat_sendvoice_voice_file_invalid), ToastType.WARN);
		}
	}
	
	/**
	 * 退出录音和ui界面.
	 * 
	 * @param needStop true表示要先停止录音，否则不需要（可能是本方法调用前就已经停止了录音）
	 * @see #stop(boolean)
	 */
	private void cancelRecording(boolean needStop)
	{
		if(needStop)
			stop(true); // 因为是用户明确取消，所以此时在停止录音时还要保证明删除录好的文件哦（防止占用SDcar空间）
		closeUI();
	}
	
	protected abstract View findViewById(int resId);
	 
	/**
	 * 开启录音.
	 */
	public void start()
	{
		// 使用异步线程提升用户的交互体验
		new AsyncTask<Object, Boolean, Boolean>()
		{
			@Override
			protected void onPreExecute()
			{
				// 代码健壮性处理：本次启动录音前先确保之前的录音已经关闭（之前的逻辑可能因错误地而没有成功停止）
				stopImpl(false);

				// * 不能在stop中重置语音时长的原因是：正常stop了，语音时长还有意义
				// * ，误清除的话调用者将无法获知该次录音的时长罗
				// 重置录音时长
				voiceRecorderWrapper.resetDuration();
				// 把时长显示也刷新（置0），否则连续录音时，上次的录音时长在本次被刷新前会显示出来，有点恶心
				refreshDurationToUI(duraionFormatter, viewDuration, voiceRecorderWrapper.getDuration());

				// 设置辅助ui显示（在后台录音模块开始前就显示出来，是为了提高用户体
				// 验，否则因语音模块启动有延迟，会有卡顿），单独弄一个启动等待的进
				// 度提示也是可以的，不过本类中倒是可以直接用录音ui来解决
				showRecordingUI(true);

				// 开始录音提示音
				PromtHelper.voiceRecordingPromt(parentActivity);

				// 启动最大录音时长延迟器
				startupMaxTimeDelayer();
		    }

			protected Boolean doInBackground(Object... urls)
			{
				try
				{
					// 要保存的录音文件路径是形如：“/sdcard/.kchat/voice/temp_39887745632133.amr”
					String tempSavedPath = SendVoiceHelper.getSendVoiceSavedDirHasSlash(parentActivity)
								+SendVoiceHelper.constructVoiceFileName(SendVoiceHelper.TEMP_VOICE_FILE_NAME_PREFIX
										+String.valueOf(System.currentTimeMillis()));
					// 开始录音
					voiceRecorderWrapper.start(tempSavedPath);
					return true;
				}
				catch (Exception e)
				{
					Log.e(TAG, "开启录音时出错了："+e.getMessage(), e);
					return false;
				}
			}

			protected void onPostExecute(Boolean result)
			{
				if(result != null && !result.booleanValue())
				{
					// 关闭最大录音时长延迟计时器
					stopMaxTimeDelayer();

					// 设置辅助ui显示
					showRecordingUI(false);

					new AlertDialog.Builder(parentActivity)
					.setTitle(R.string.general_prompt)
					.setMessage(parentActivity.getString(R.string.chat_sendvoice_startup_recorder_faild))
					.setPositiveButton(R.string.general_yes, new DialogInterface.OnClickListener(){
						@Override
						public void onClick(DialogInterface dialog,int which)
						{
							stop(true); // 因为是录音出错了，所以此时在停止录音时还要保证明删除录好的文件哦（防止占用SDcar空间）
							closeUI();
						}
					})
					.show();
				}
				else
				{
//					layoutOfPrepareProgress.setVisibility(View.GONE);
//					layoutOfRecording.setVisibility(View.VISIBLE);

//					// 设置辅助ui显示
//					showRecordingUI(true);
				}
			}
		}.execute();
	}
	
	/**
	 * 尝试停止录音（如果当前正在录音的话）.
	 * 
	 * <p>
	 * 说明：除非是明确需要删除录音文件的情况，其它情况下建议不要尝试删除，因为在某些情况下可能正常的上传
	 * 等处理操作尚未完成而文件可能就被删除了。程序里可以作一个自动清理的优化即可保证这些无意义的垃圾文件
	 * 不要待在SD卡上（节省SD卡空间）。
	 * 
	 * @param deleteFile true表示停止后立即删除保存的录音数据文件，否则不删除。比如在录音操作
	 * 中有些环节出问题了，此次录音被取消时，该录音文件就没有存在意义了（省的占用SD卡空间），需要删除它
	 * @return 如果正常关闭录音则返回录音文件保存路径，否则返回null
	 */
	public String stop(boolean deleteFile)
	{
		String savedPath = stopImpl(deleteFile);

		// 设置辅助ui显示
		showRecordingUI(false);
		// 重置录音时长的显示时间
		refreshDurationToUI(duraionFormatter, viewDuration, 0);

		return savedPath;
	}
	private String stopImpl(boolean deleteFile)
	{
		// 关闭最大录音时长延迟器
		stopMaxTimeDelayer();
		
		// 停止录音及相关处理
		if(voiceRecorderWrapper.isRecording())
		{
			String savedPath = null;
			try
			{
				// 停止录音并把录音保存的文件路径保存起来
				savedPath = voiceRecorderWrapper.stop();
				try{
					if(deleteFile)
						FileHelper.deleteFile(savedPath);
				}
				catch (Exception e){
					Log.e(TAG, e.getMessage(), e);
				}
				
				return savedPath;
			}
			catch (Exception e)
			{
				Log.e(TAG, "停止录音时出错了："+e.getMessage()+"("+savedPath+")", e);
			}
		}
		
		return null;
	}
	
	/**
	 * 设置辅助ui显示.
	 * 
	 * @param visible
	 */
	private void showRecordingUI(boolean visible)
	{
		// 当“录音中”时，显示录音中动画和录音中相关组件
		if(visible)
		{
			// * 开启"录音中"动画
			viewLightEfect.startAnimation(animLightEfect);
			// 注意：上面xml中定义interpolator会出现转一圈后停顿一下。
			// Android有这么一个类LinearInerpolator：这个类是Inerpolator的子类，是实现匀速动画的
			// 关键。但是不能通过XML中的那个android:interpolator属性去设置，必须在java代码中去实现
			animLightEfect.setInterpolator(new LinearInterpolator());
			
//			layoutOfPrepareProgress.setVisibility(View.GONE);
			layoutOfRecording.setVisibility(View.VISIBLE);
		}
		// 否则显示“准备中”的无穷进度条
		else
		{
//			layoutOfPrepareProgress.setVisibility(View.VISIBLE);
			layoutOfRecording.setVisibility(View.GONE);
			
			// * 关闭"录音中"动画
			viewLightEfect.clearAnimation();
			animLightEfect.setInterpolator(null);
		}
	}
	
	/**
	 * 点击界面上的“取消发送”按钮后，将会调用本方法，用于关闭ui等处理.
	 */
	public abstract void closeUI();
	
	/**
	 * 音量图片动画刷新方法.
	 * 
	 * @param voiceValue
	 */
	public static void refreshVolumnAnimationToUI(ImageView viewVolumn, double voiceValue)
	{
		if (voiceValue < 200.0) 
			viewVolumn.setImageResource(R.drawable.record_animate_01);
		else if (voiceValue > 200.0 && voiceValue < 400)
			viewVolumn.setImageResource(R.drawable.record_animate_02);
		else if (voiceValue > 400.0 && voiceValue < 800) 
			viewVolumn.setImageResource(R.drawable.record_animate_03);
		else if (voiceValue > 800.0 && voiceValue < 1600) 
			viewVolumn.setImageResource(R.drawable.record_animate_04);
		else if (voiceValue > 1600.0 && voiceValue < 3200) 
			viewVolumn.setImageResource(R.drawable.record_animate_05);
		else if (voiceValue > 3200.0 && voiceValue < 5000) 
			viewVolumn.setImageResource(R.drawable.record_animate_06);
		else if (voiceValue > 5000.0 && voiceValue < 7000)
			viewVolumn.setImageResource(R.drawable.record_animate_07);
		else if (voiceValue > 7000.0 && voiceValue < 10000.0)
			viewVolumn.setImageResource(R.drawable.record_animate_08);
		else if (voiceValue > 10000.0 && voiceValue < 14000.0)
			viewVolumn.setImageResource(R.drawable.record_animate_09);
		else if (voiceValue > 14000.0 && voiceValue < 17000.0) 
			viewVolumn.setImageResource(R.drawable.record_animate_10);
		else if (voiceValue > 17000.0 && voiceValue < 20000.0) 
			viewVolumn.setImageResource(R.drawable.record_animate_11);
		else if (voiceValue > 20000.0 && voiceValue < 24000.0) 
			viewVolumn.setImageResource(R.drawable.record_animate_12);
		else if (voiceValue > 24000.0 && voiceValue < 28000.0)
			viewVolumn.setImageResource(R.drawable.record_animate_13);
		else if (voiceValue > 28000.0) 
			viewVolumn.setImageResource(R.drawable.record_animate_14);
	}
	
//	/**
//	 * 录音时长刷新方法.
//	 * 
//	 * @param millisecond
//	 */
//	private void refreshDurationToUI(long millisecond)
//	{
//		// **  刷新ui显示
//		String mm_ss = duraionFormatter.format(millisecond);  
//		viewDuration.setText(mm_ss);
//	}
	/**
	 * 录音时长刷新方法.
	 * 
	 * @param millisecond
	 */
	public static void refreshDurationToUI(SimpleDateFormat duraionFormatter, TextView viewDuration, long millisecond)
	{
		// **  刷新ui显示
		String mm_ss = duraionFormatter.format(millisecond);  
		viewDuration.setText(mm_ss);
	}
	
	private void startupMaxTimeDelayer()
	{
		// 启动前先尝试关闭最长录音时长的延迟器
		stopMaxTimeDelayer();
		
		// 注意：因最大时间是使用VoiceRecorder中的timer实现，所以可能voiceRecorderWrapper.getDuration()并不严格准确！
		delayedHandler = new DelayedHandler((int)SendVoiceHelper.MAX_DURATION_FOR_SEND){
			@Override
			protected void fireRun()
			{
				// 先停止录音
				final String tempVoicePath = SendVoiceWrapper.this.stop(false); // 正常停止录音（无需删除录音文件哦）

				// 再弹出一个操作提示对话框
				new AlertDialog.Builder(parentActivity)
					.setTitle(R.string.general_prompt)
					.setMessage(parentActivity.getString(R.string.chat_playvoice_send_for_at_max_hint))
					.setPositiveButton(R.string.chat_playvoice_send_for_at_max, new DialogInterface.OnClickListener(){
						@Override
						public void onClick(DialogInterface dialog,int which)
						{
							closeUI();
							// 发送（不是调用sendVoice的原因是：已经在此之前就停止了录音了！）
							sendVoiceImpl(tempVoicePath);
						}
					}) 
					.setNegativeButton(R.string.general_cancel, new DialogInterface.OnClickListener(){
						@Override
						public void onClick(DialogInterface dialog,int which)
						{
							closeUI();
							// 取消（除了关停ui外什么也不用干）
							cancelRecording(false);
						}
					}) 
					.show(); 
			}
		};
	}
	private void stopMaxTimeDelayer()
	{
		if(delayedHandler != null)
		{
			delayedHandler.stop();
			delayedHandler = null;
		}
	}
	
	//--------------------------------------------------------------------------------------- inner class
	/**
	 * 临时存储的声音文件名过滤器实现类.
	 * 
	 * @author Jack Jiang, 2014-02-20
	 */
	public static class TempVoiceFilter implements FilenameFilter
	{   
		private String prefix = null;
		
		public TempVoiceFilter(String prefix)
		{
			this.prefix = prefix;
		}
		
		// 前缀是temp_打头的即被认为是临时文件
		public boolean isTemp(String file)
		{   
			if (file != null && file.startsWith(prefix
//					SendVoiceHelper.TEMP_VOICE_FILE_NAME_PREFIX
					))
				return true;   
			else
				return false;   
		}   
		public boolean accept(File dir,String fname)
		{   
			return isTemp(fname);   
		}   
	}
}
